﻿namespace ServerQuery
{
    using CustomCharacter;
    using Exiled.API.Features;
    using Exiled.API.Features.Pools;
    using JsonModel;
    using MEC;
    using MuteRemastered;
    using Newtonsoft.Json;
    using RemoteAdmin;
    using RgCore.Database;
    using RgCore.GameData;
    using RgCore.Menu;
    using RgCore.Modules;
    using RgCore.PlayerHud;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using VoiceChat;
    using Color = System.Drawing.Color;
    public sealed class QueryConfig : IConfig
    {
        [Description("主机IP地址")]
        public string IpAddress { get; set; } = "127.0.0.1";

        [Description("通讯端口")]
        public uint Port { get; set; } = 3010;
    }

    public class ServerInfo
    {
        public ServerInfo(string name, ushort gamePort, ushort queryPort)
        {
            Name = name;
            GamePort = gamePort;
            QueryPort = queryPort;
        }

        public string Name { get; set; }
        public ushort GamePort { get; set; }
        public ushort QueryPort { get; set; }
    }
    public class ServerQueryPlugin : RgPlugin
    {
        public override object Config { get; set; } = new QueryConfig();
        public override bool CanUnload => true;

        public override string Name => "服务器查询";

        public override string Id => "server_query";
        public static ServerQueryPlugin instance;
        public static string JsonPath => Path.Combine(Paths.Exiled, "ServerQuery_RelatedServers.json");
        public ServerInfo[] Servers = new ServerInfo[]
        {
            new ServerInfo("测试1", 7777, 3010),
            new ServerInfo("测试2", 7778, 3011)
        };
        public RelatedServer[] Cache = null;
        public DateTime LastQuery = DateTime.MinValue;
        public Page RootPage = null;

        public override void OnDisabled()
        {
            instance = null;
            SocketData.Awake(false);
            base.OnDisabled();
            Exiled.Events.Handlers.Player.Banned -= Player_Banned;
            SetMenuEnabled(false);
        }

        public override void OnEnabled()
        {
            instance = this;
            SocketData.Awake(true);
            base.OnEnabled();
            Exiled.Events.Handlers.Player.Banned += Player_Banned;
            RegisterOtherServer();
            SetMenuEnabled(true);
        }
        public void SetMenuEnabled(bool enabled)
        {
            if (enabled)
            {
                RootPage = new Page(MenuInterface.Root, "RelatedServers", "相关服务器", null, null, null, false)
                {
                    AwaitRequired = true,
                };
                RootPage.Children =  (player) =>
                {
                    List<MenuElement> list = ListPool<MenuElement>.Pool.Get();
                    list.Add(new Text(RootPage, "", () => "<color=#FFDEAD>服务器状态最短15秒更新一次</color>", false));
                    if ((DateTime.Now - LastQuery).TotalSeconds > 15 || Cache == null)
                        Cache = SocketData.GetServerStatus().Result;
                    RelatedServer[] servers = Cache;
                    for (int i = 0; i < servers.Length; i++)
                    {
                        var server = servers[i];
                        int id = i + 1;
                        string status = "<color=#B22222>离线</color>";
                        string hex = "#555555";
                        string p = server.Data?.Players.ToString() ?? "N";
                        string max = server.Data?.MaxPlayers.ToString() ?? "A";
                        float percent = 0f;
                        if (server.Data != null)
                        {
                            percent = (float)server.Data.Players / server.Data.MaxPlayers;
                            hex = "#00FF00";
                            if (percent >= 0.5f)
                                hex = "yellow";
                            if (percent >= 0.85f)
                                hex = "#FF7F24";
                            if (percent >= 1f)
                                hex = "#FF0000";
                            status = server.Data.RoundTime == -1 ? "<color=#32CD32>等待玩家中</color>" : $"<color=#CD5555>已开局{server.Data.RoundTime}分钟</color>";
                        }
                        Page page = new Page(RootPage, $"Server_{id}", $"[#{id}] {server.Name}", (_) =>                          
                            $"{server.Name} 「{status}」(<color={hex}>{p}/{max}</color>)"
                        , null, null, false);
                        page.Children = (p2) => new MenuElement[]
                        {
                            new Text(page, "", () => $"<color=#00FF00>玩家</color>：{p} / {max}", false),
                            new Text(page, "", () => $"<color=#00FF00>状态</color>：{status}", false),
                            new Option(page,$"Redirect_{i + 1}","跳转至该服务器", null, null, (ply) =>
                            {
                                 if (Server.Port == server.Port)
                                {
                                    return "你当前已经在这个服务器了！";
                                }
                                 if (server.Data == null)
                                {
                                    return "指定服务器离线";
                                }
                                 var mi = MenuInterface.MenuInterfaces.FirstOrDefault(x => x.Player == ply);
                                if (mi == null)
                                {
                                    return "出现了错误，请重新启动菜单";
                                }
                                mi.ShowConfirm(new string[]
                                {
                                    "<color=#F4A460>确认更换服务器游玩后，系统会为你重定向到指定服务器</color>",
                                    "<color=#F4A460>此功能不保证你能够稳定加入新的服务器</color>"
                                }, (ply2) =>
                                {
                                    ply2.SendFormattedMsg("migration", "<color=#FF1493>服务器重定向</color>", "将在 3 秒后为你重定向到新的服务器，祝你好运！", 3u, MsgType.Success_Response, ScreenLocation.CenterTop, true);
                                    Timing.CallDelayed(3.0f, delegate ()
                                    {
                                        ply2.Reconnect(server.Port, 2f, roundRestartType: RoundRestarting.RoundRestartType.RedirectRestart);
                                    });
                                });
                                return "";
                            }, false){DontRefresh = true}
                        };
                        list.Add(page);
                    }
                    return ListPool<MenuElement>.Pool.ToArrayReturn(list);
                };
                MenuInterface.RegisteredElements.Add(RootPage);
            }
            else
            {
                MenuInterface.RegisteredElements.Remove(RootPage);
                RootPage = null;
            }
        }
        public async void RegisterOtherServer()
        {
            await Task.Run(delegate ()
            {
                if (!File.Exists(JsonPath))
                {
                    File.WriteAllText(JsonPath, JsonConvert.SerializeObject(Servers));
                }
                else
                {
                    Servers = JsonConvert.DeserializeObject<ServerInfo[]>(File.ReadAllText(JsonPath));
                }
            });
        }
        private void Player_Banned(Exiled.Events.EventArgs.Player.BannedEventArgs ev)
        {
            if (ev.Player != null)
            {
                TimeSpan ts = new TimeSpan(ev.Details.Expires - ev.Details.IssuanceTime);
                try
                {
                    SocketData.IssueViolation(ev.Target.UserId, ev.Details.Reason, ts.Minutes, $"{ev.Player.Nickname}({ev.Player.UserId})");
                }
                catch { }
            }
        }
    }
    public readonly struct RelatedServer
    {
        public readonly string Name;
        public readonly ushort Port;
        public readonly ServerData Data;

        public RelatedServer(string name, ushort port, ServerData data)
        {
            Name = name;
            Port = port;
            Data = data;
        }
    }
    public static class SocketData
    {
        public static bool Success;
        public static string Response;
        public static Socket ServerSocket;
        public static Socket Socket;
        public static Thread Thr;

        public static int Coolq_port => (int)((QueryConfig)ServerQueryPlugin.instance.Config).Port;

        public static async Task<RelatedServer[]> GetServerStatus()
        {
            List<RelatedServer> servers = ListPool<RelatedServer>.Pool.Get();
            List<Task<string>> tasks = ListPool<Task<string>>.Pool.Get();
            string msg = JsonConvert.SerializeObject(new ExecutionRequest(){Request = Request.StatusQuery});
            var serverlist = ServerQueryPlugin.instance.Servers.ToList();
            foreach (var server in serverlist)
            {
                tasks.Add(SendMsg(server.QueryPort, msg));
            }
            var result = await Task.WhenAll(tasks);
            for (int i = 0; i < result.Length; i++)
            {
                ServerData sd = string.IsNullOrEmpty(result[i]) || result[i] == "Offline" ? 
                    null : JsonConvert.DeserializeObject<ServerData>(result[i]);
                var server = serverlist[i];
                servers.Add(new RelatedServer(server.Name,server.GamePort,  sd));
            }
            ListPool<Task<string>>.Pool.Return(tasks);
            ServerQueryPlugin.instance.LastQuery = DateTime.Now;
            return ListPool<RelatedServer>.Pool.ToArrayReturn(servers);
        }
        public static async Task<string> SendMsg(ushort port, string msg)
        {
            string response;
            string ipstr = (ServerQueryPlugin.instance.Config as QueryConfig).IpAddress;
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);
            IPAddress ip = System.Net.IPAddress.TryParse(ipstr, out ip) ? ip : Dns.GetHostEntry(ipstr).AddressList[0];
            try
            {
                await socket.ConnectAsync(new IPEndPoint(ip, port));
                socket.Send(Encoding.UTF8.GetBytes(msg));
                byte[] buffer = new byte[1024];
                int count = socket.Receive(buffer);
                response = Encoding.UTF8.GetString(buffer, 0, count);
                socket.Close();
            }
            catch (SocketException)
            {
                response = "Offline";
                socket.Close();
            }
            return response;
        }
        public static void Awake(bool IsAwake = true)
        {
            if (IsAwake)
            {
                Log.Info("服务器Socket查询系统将在10秒后启动");
                Timing.CallDelayed(10f, () =>
                {
                    try
                    {
                        Log.Info($"服务器Socket查询系统将在端口【{Coolq_port}】启动");
                        Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        Socket.Bind(new IPEndPoint(IPAddress.Parse(((QueryConfig)ServerQueryPlugin.instance.Config).IpAddress), Coolq_port));
                        Socket.Listen(5);
                        Thr = new Thread(Listen)
                        {
                            IsBackground = true
                        };
                        Thr.Start();
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                    }
                });
            }
            else
            {
                try
                {
                    Thr.Abort();
                    Socket.Close();
                    Socket.Disconnect(false);
                    Socket.Dispose();
                }
                catch { }
            }
        }

        public static void ExecuteCommand(string cmd, out bool success, out string response)
        {
            CommandProcessor.ProcessQuery(cmd, BotCommandSender.Instance);
            success = SocketData.Success;
            response = SocketData.Response;
        }

        public static string[] GetCustomRole(Player player)
        {
            if (MethodExtensions.IsCustomCharacter(player))
            {
                var inst = MethodExtensions.GetCharacterInstance(player);
                return new string[] { inst.Attribute.Name, inst.RoleColor.ToHex() };
            }
            return new string[] { player.Role.Name, player.Role.Color.ToHex() };
        }

        public static void Listen()
        {
            while (true)
            {
                try
                {
                    ServerSocket = Socket.Accept();
                    byte[] buffer = new byte[1024];
                    int count = ServerSocket.Receive(buffer);
                    string str = Encoding.UTF8.GetString(buffer, 0, count);
                    ExecutionRequest req = JsonConvert.DeserializeObject<ExecutionRequest>(str);
                    if (req == null)
                    {
                        Log.Info("接收到了请求，但无法反序列成有效内容");
                        ServerSocket.Close();
                        continue;
                    }
                    switch (req.Request)
                    {
                        case Request.SignIn:
                            {
                                string userid = (string)req.Parameters["userid"];
                                int amo = int.TryParse(req.Parameters["amount"].ToString(), out int x) ? x : 0;
                                Player player = Player.Get(userid);
                                ExecutionResponse rep = new ExecutionResponse
                                {
                                    Success = false
                                };
                                if (player != null)
                                {
                                    rep.Success = true;
                                    GameplayData.GameDatas.FirstOrDefault(y => y.Player == player).AddPoints(amo, true);
                                }
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                Log.Info("已完成客户端请求：玩家签到");
                                break;
                            }
                        case Request.PlayerList:
                            {
                                List<PlayerData> players = new List<PlayerData>();
                                foreach (Player player in Player.List)
                                {
                                    GameplayData gd = GameplayData.GameDatas.FirstOrDefault(x => x.Player == player);
                                    PlayerData pd = new PlayerData()
                                    {
                                        Nickname = player.Nickname,
                                        OnlineData = new OnlineData()
                                        {
                                            Id = player.Id,
                                            RoleColorHex = player.Role.Color.ToHex()
                                        }
                                    };
                                    players.Add(pd);
                                }
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(players)));
                                Log.Info("已完成客户端请求：玩家列表查询");
                                break;
                            }
                        case Request.StatusQuery:
                            {
                                ServerData sd = new ServerData()
                                {
                                    Players = Player.List.Count,
                                    MaxPlayers = Server.MaxPlayerCount,
                                    RoundTime = Round.IsStarted ? Round.ElapsedTime.Minutes : -1,
                                    OnlineAdmin = Player.List.Count(x => x.RemoteAdminAccess)
                                };
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(sd)));
                                Log.Info("已完成客户端请求：服务器查询");
                                break;
                            }
                        case Request.PlayerInformation:
                            {
                                Player player;
                                object idn = req.Parameters["userid"];
                                if (int.TryParse(idn.ToString(), out int id))
                                    player = Player.Get(id);
                                else
                                    player = Player.Get(idn.ToString());
                                ExecutionResponse rep = new ExecutionResponse() { Success = false };
                                if (player != null)
                                {
                                    GameplayData gd = GameplayData.GameDatas.FirstOrDefault(x => x.Player == player);
                                    string[] role = new string[] { player.Role.Name, player.Role.Color.ToHex() };
                                    if (ModuleManager.IsLoaded("CustomCharacter"))
                                    {
                                        role = GetCustomRole(player);
                                    }
                                    OnlineData od = new OnlineData()
                                    {
                                        UserId = player.UserId,
                                        Id = player.Id,
                                        Experience = gd.Experience,
                                        IcomMuted = player.IsIntercomMuted,
                                        Level = gd.Level,
                                        Mvp = gd.Mvp,
                                        Points = gd.Points,
                                        Muted = player.IsMuted,
                                        RoleColorHex = role[1],
                                        RoleName = role[0]
                                    };
                                    rep.Success = true;
                                    rep.Response.Add("data", JsonConvert.SerializeObject(od));
                                }
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                Log.Info("已完成客户端请求：玩家查询");
                                break;
                            }
                        case Request.CommandExecute:
                            {
                                string cmd = (string)req.Parameters["cmd"];
                                ExecuteCommand(cmd, out bool success, out string response);
                                ExecutionResponse rep = new ExecutionResponse()
                                {
                                    Success = success
                                };
                                rep.Response.Add("response", response);
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                Log.Info($"已完成客户端请求：执行命令（{cmd}）");
                                break;
                            }
                        case Request.Ban:
                            {
                                object idn = req.Parameters["userid"]; //用户ID
                                int duration = int.TryParse(req.Parameters["duration"].ToString(), out int x) ? x : 0; //时长(分)
                                string reason = (string)req.Parameters["reason"]; //原因
                                bool specified = (bool)req.Parameters["specified"]; //特指
                                Player player;
                                if (int.TryParse(idn.ToString(), out int id))
                                    player = Player.Get(id);
                                else
                                    player = Player.Get(idn.ToString());
                                if (player != null)
                                {
                                    ExecutionResponse rep = new ExecutionResponse()
                                    {
                                        Success = true
                                    };
                                    rep.Response.Add("nickname", player.Nickname);
                                    rep.Response.Add("userid", player.UserId);
                                    Log.Info("收到客户端封禁命令：" + player.Nickname);
                                    if (duration == 0)
                                        BanPlayer.KickUser(player.ReferenceHub, reason);
                                    else
                                        BanPlayer.BanUser(player.ReferenceHub, reason, duration * 60);
                                    ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                }
                                else if (specified)
                                {
                                    Log.Info("收到客户端离线封禁命令：" + idn.ToString());
                                    BanHandler.IssueBan(new BanDetails()
                                    {
                                        OriginalName = "OFFLINE-BAN",
                                        Id = idn.ToString(),
                                        IssuanceTime = TimeBehaviour.CurrentTimestamp(),
                                        Expires = TimeBehaviour.GetBanExpirationTime((uint)duration * 60),
                                        Issuer = "SERVER",
                                        Reason = reason
                                    }, BanHandler.BanType.UserId);
                                    ExecutionResponse rep = new ExecutionResponse()
                                    {
                                        Success = true
                                    };
                                    rep.Response.Add("nickname", "【离线封禁】");
                                    rep.Response.Add("userid", idn.ToString());
                                    ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                }
                                else
                                {
                                    ExecutionResponse rep = new ExecutionResponse()
                                    {
                                        Success = false
                                    };
                                    ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                }
                                break;
                            }
                        case Request.Unban:
                            {
                                Log.Info("收到客户端解封命令：" + req.Parameters["userid"]);
                                BanHandler.RemoveBan(req.Parameters["userid"].ToString(), BanHandler.BanType.UserId, false);
                                ExecutionResponse rep = new ExecutionResponse()
                                {
                                    Success = true
                                };
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                break;
                            }
                        case Request.Mute:
                            {
                                string userid = req.Parameters["userid"].ToString();
                                int duration = int.Parse(req.Parameters["minutes"].ToString());
                                bool specified = (bool)req.Parameters["specified"];
                                Player player = Player.Get(userid);
                                Log.Info("收到客户端禁言命令：" + userid);
                                ExecutionResponse rep = new ExecutionResponse()
                                {
                                    Success = true
                                };
                                if (int.TryParse(userid, out int id))
                                {
                                    player = Player.Get(id);
                                    if (player == null)
                                    {
                                        rep.Success = false;
                                        rep.Response.Add("response", "找不到指定的玩家");
                                    }
                                    else
                                        userid = player.UserId;
                                }
                                if (player == null && !specified)
                                {
                                    rep.Success = false;
                                }
                                if (rep.Success)
                                { 
                                    if (ModuleManager.IsLoaded("mute_remastered"))
                                    {
                                        AddMute(userid, duration, false);
                                    }
                                    else
                                        VoiceChatMutes.IssueLocalMute(userid);
                                    rep.Response.Add("nickname", player?.Nickname ?? "离线玩家");
                                    rep.Response.Add("userid", player?.UserId ?? userid);
                                }
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                break;
                            }
                        case Request.Unmute:
                            {
                                string userid = req.Parameters["userid"].ToString();
                                Player player = Player.Get(userid);
                                Log.Info("收到客户端解除禁言命令：" + userid);
                                ExecutionResponse rep = new ExecutionResponse()
                                {
                                    Success = true
                                };
                                if (int.TryParse(userid, out int id))
                                {
                                    player = Player.Get(id);
                                    if (player == null)
                                    {
                                        rep.Success = false;
                                        rep.Response.Add("response", "找不到指定的玩家");
                                    }
                                    else
                                        userid = player.UserId;
                                }
                                if (rep.Success)
                                {
                                    if (ModuleManager.IsLoaded("mute_remastered"))
                                    {
                                        RemoveMute(userid);
                                    }
                                    else
                                        VoiceChatMutes.RevokeLocalMute(userid);
                                    rep.Response.Add("nickname",player?.Nickname ?? "离线玩家");
                                    rep.Response.Add("userid", player?.UserId ?? userid);
                                }
                                ServerSocket.Send(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rep)));
                                break;
                            }
                    }
                }
                catch (Exception e)
                {
                    Log.Error(e);
                    ServerSocket.Close();
                    continue;
                }
            }
        }
        public static void AddMute(string userid, int minute, bool intercom)
        {
            MuteRemastered.AddMute(userid, minute, intercom);
        }
        public static void RemoveMute(string userid)
        {
            MuteRemastered.RemoveMute(userid);
        }
        public static async void IssueViolation(string id, string reason, int duration, string issuer)
        {
            if (DatabaseController.DatabaseDisabled) return;
            Log.Info($"执行封禁：{id} <- {issuer} 时长：{duration} 原因：{reason}");
            NoResponseCommandRequest cmd = new NoResponseCommandRequest("violation", RequestType.Insert, new Dictionary<string, object>
            {
                {"id", id }, {"violation_description", reason }, {"violation_datetime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, {"duration", duration}, {"issuer", issuer}
            }, null);
            await cmd.ProcessNonQuery();
        }
    }

    public class Badge
    {
        public Color Color { get; set; }

        public string Text { get; set; }
    }

    public class BotCommandSender : CommandSender
    {
        public static BotCommandSender Instance = new BotCommandSender();
        public override bool FullPermissions => true;

        public override byte KickPower => byte.MaxValue;

        public override string Nickname => "RCON-Server";

        public override ulong Permissions => ServerStatic.PermissionsHandler.FullPerm;

        public override string SenderId => "Rcon";

        public override void Print(string text)
        {
            Log.Info("[QQBot Print] " + text);
        }

        public override void RaReply(string text, bool success, bool logToConsole, string overrideDisplay)
        {
            SocketData.Response = text;
            SocketData.Success = success;
        }
    }
}